package main

import (
	"fmt"
	"sync"
)

// 演示资源竞争
/**
多个协程访问同一块内存，会出现资源竞争的情况
*/
var sum = 0

//var mutex sync.Mutex
var mutex sync.RWMutex
var wg sync.WaitGroup

func add(i int) {
	/**
	小提示：以上被加锁保护的 sum+=i 代码片段又称为临界区。在同步的程序设计中，
	临界区段指的是一个访问共享资源的程序片段，而这些共享资源又有无法同时被多个协程访问的特性。
	当有协程进入临界区段时，其他协程必须等待，这样就保证了临界区的并发安全。
	*/
	mutex.Lock()
	defer mutex.Unlock()

	sum += i

}

func readSum() int {
	mutex.RLock()
	defer mutex.RUnlock()
	b := sum
	return b
}

/**
sync.WaitGroup 的使用比较简单，一共分为三步：
1.声明一个 sync.WaitGroup，然后通过 Add 方法设置计数器的值，需要跟踪多少个协程就设置多少，这里是 110；
2.在每个协程执行完毕时调用 Done 方法，让计数器减 1，告诉 sync.WaitGroup 该协程已经执行完毕；
3.最后调用 Wait 方法一直等待，直到计数器值为 0，也就是所有跟踪的协程都执行完毕。

注意：一定要在每个协程执行完毕时调用Done方法 而不是协程中调用方法执行完毕的时候

通过 sync.WaitGroup 可以很好地跟踪协程。在协程执行完毕后，整个 run 函数才能执行完毕，时间不多不少，正好是协程执行的时间。
sync.WaitGroup 适合协调多个协程共同做一件事情的场景，比如下载一个文件，假设使用 10 个协程，每个协程下载文件的 1/10 大小，
只有 10 个协程都下载好了整个文件才算是下载好了。这就是我们经常听到的多线程下载，通过多个线程共同做一件事情，显著提高效率。
小提示：其实你也可以把 Go 语言中的协程理解为平常说的线程，从用户体验上也并无不可，但是从技术实现上，你知道他们是不一样的就可以了。
*/
func main() {
	wg.Add(110)

	for i := 0; i < 100; i++ {
		go func() {
			add(1)
			defer wg.Done() // 这里的wg.Done() 必须写在协程结束前的最后一行，add方法之后
		}()
	}
	for i := 0; i < 10; i++ {
		go func() {
			defer wg.Done()
			fmt.Println("现在s的值为：", readSum())
		}()
	}
	wg.Wait()
}
